##
# Author: Samuel Abels <http://debain.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
##
import os.path
import pango
import gobject
import gtk
import gtk.glade
import util
import vte
from gettext         import gettext as _
from config          import c as cfg
from AboutDialog     import AboutDialog
from Workflow        import Workflow
from WorkflowDisplay import WorkflowDisplay

class MainWindow(gobject.GObject):
    def __init__(self, controller):
        gobject.GObject.__init__(self)
        self.controller        = controller
        gladefile              = cfg['APP_SYSNAME'] + '.glade'
        self.xml               = util.get_glade_xml(gladefile, 'window_nanny')
        self.window            = self.xml.get_widget('window_nanny')
        self.notebook_terminal = self.xml.get_widget('notebook_terminal')
        self.toolbar           = self.xml.get_widget('toolbar')
        self.menubar           = self.xml.get_widget('menubar')
        self.terminals         = {}
        self.workflows         = {}
        self.font_size         = None
        self.default_font_size = None
        self.ignore_signal     = False

        self._add_terminal()
        self.notebook_terminal.remove_page(0)
        self.xml.signal_autoconnect(self)
        self.window.connect('focus-in-event', self._focus_terminal)
        self.window.connect('delete-event',   self._quit)
        self.window.show_all()

        # Set up a recent files menu.
        recent  = self.xml.get_widget('menu_recent')
        manager = gtk.recent_manager_get_default()
        menu    = gtk.RecentChooserMenu(manager)
        menu.set_sort_type(gtk.RECENT_SORT_MRU)
        menu.set_local_only(False)
        menu.set_show_numbers(True)
        menu.set_show_icons(False)
        filter  = gtk.RecentFilter()
        filter.add_application('nanny')
        filter.add_mime_type('application/nanny')
        menu.add_filter(filter)
        menu.connect('item-activated', self._recent_item_activated_cb)
        recent.set_submenu(menu)

        # Work around a Glade bug that some accelerators are broken.
        accel_group = gtk.AccelGroup()
        self.window.add_accel_group(accel_group)

        item = self.xml.get_widget('menu_workflow_create')
        item.add_accelerator('activate',
                             accel_group,
                             ord('n'),
                             gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK,
                             gtk.ACCEL_VISIBLE)

        item = self.xml.get_widget('menu_workflow_select')
        item.add_accelerator('activate',
                             accel_group,
                             ord('s'),
                             gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK,
                             gtk.ACCEL_VISIBLE)

        item = self.xml.get_widget('menu_view_zoom_in')
        item.add_accelerator('activate',
                             accel_group,
                             ord('+'),
                             gtk.gdk.CONTROL_MASK,
                             gtk.ACCEL_VISIBLE)
        item = self.xml.get_widget('menu_view_zoom_out')

        item.add_accelerator('activate',
                             accel_group,
                             ord('-'),
                             gtk.gdk.CONTROL_MASK,
                             gtk.ACCEL_VISIBLE)
        self._notebook_terminal_switch_page_cb(self.notebook_terminal, 0, 0)


    def _open_connection(self, term):
        term.fork_command('bash')


    def _confirm_close(self, term):
        workflow = self._get_workflow(term)
        title    = _('Cancel the workflow "%s"?') % workflow.get_name()
        title    = '<span weight="bold" size="larger">%s</span>' % title
        body     = _('The workflow is still running. By closing the tab you'
                   + ' will also stop the workflow.\n'
                   + 'Are you sure you want to close this tab?')
        cancel   = gtk.Button(stock = 'gtk-cancel')
        discard  = gtk.Button(stock = 'gtk-delete')
        discard.get_child().get_child().get_children()[1].set_text('Close Tab')
        dialog = gtk.MessageDialog(type    = gtk.MESSAGE_WARNING,
                                   buttons = gtk.BUTTONS_NONE)
        dialog.set_markup(title + '\n\n' + body)
        dialog.add_action_widget(cancel,  0)
        dialog.add_action_widget(discard, 1)
        dialog.show_all()
        response = dialog.run()
        dialog.destroy()
        if response == 0:
            return False
        return True


    ################################################################
    # Workflow functions.
    ################################################################
    def open_workflow(self, workflow):
        term = self._get_current_terminal()
        if term:
            if self._workflow_is_running(term) \
              and not self._confirm_close(term):
                return
            self._workflow_stop(term)
        else:
            term = self._add_terminal()

        self._workflow_start(term, workflow)
        self.controller.workflow_opened(workflow)


    def workflow_changed(self, workflow):
        self._update_workflow_displays(workflow)


    def _get_workflow_area(self, term):
        paned = term.get_parent()
        return paned.get_children()[1]


    def _get_workflow_display(self, term):
        wf_area  = self._get_workflow_area(term)
        children = wf_area.get_children()
        if len(children) == 0:
            return None
        return children[0]


    def _update_workflow_display_font(self, display):
        context = display.get_pango_context()
        font    = context.get_font_description()
        font.set_size(self.font_size)
        display.modify_font(font)


    def _update_workflow_displays(self, workflow):
        n_pages = self.notebook_terminal.get_n_pages()
        for n in range(0, n_pages):
            page    = self.notebook_terminal.get_nth_page(n)
            term    = self.terminals[page]
            display = self._get_workflow_display(term)
            if display is None:
                continue
            if display.workflow.get_name() == workflow.get_name():
                display.workflow.set_deleted(workflow.is_deleted())
            display.update()


    def _workflow_start(self, term, workflow):
        self.workflows[term] = workflow

        # Add actions to the workflow UI.
        paned   = term.get_parent()
        display = WorkflowDisplay(self.controller, workflow)
        wf_area = self._get_workflow_area(term)
        wf_area.add(display)
        wf_area.set_size_request(200, 200)
        self._update_workflow_display_font(display)
        alloc = self.window.get_allocation()
        paned.set_position(int(alloc.height - (alloc.height / 1.8)))
        display.connect('edit-clicked', self._display_edit_clicked_cb)
        wf_area.show_all()

        # Trigger events to update the (tool)button sensitivity and the 
        # terminal title.
        page = self.notebook_terminal.get_current_page()
        self._notebook_terminal_switch_page_cb(self.notebook_terminal, 0, page)


    def _workflow_pause(self, term):
        pass


    def _workflow_stop(self, term):
        # Find the widget of the current workflow.
        display = self._get_workflow_display(term)
        if display is None:
            return

        # Remove the widget from the UI.
        paned   = term.get_parent()
        wf_area = self._get_workflow_area(term)
        wf_area.remove(display)
        paned.set_position(10000)
        wf_area.set_size_request(0, 0)

        # Destroy the workflow.
        if self.workflows.has_key(term):
            #self.workflows[term].stop()
            del self.workflows[term]

        # Trigger events to update the (tool)button sensitivity and the 
        # terminal title.
        page = self.notebook_terminal.get_current_page()
        self._notebook_terminal_switch_page_cb(self.notebook_terminal, 0, page)


    def _get_workflow(self, term):
        return self.workflows.get(term)


    def _workflow_is_open(self, term):
        if not self.workflows.has_key(term):
            return False
        return True


    def _workflow_is_running(self, term):
        if not self.workflows.has_key(term):
            return False
        if self.workflows[term].is_completed():
            return False
        return True


    def _display_edit_clicked_cb(self, display, workflow):
        self.emit('edit-workflow', workflow)


    ################################################################
    # Terminal functions.
    ################################################################
    def _add_terminal(self):
        # Create widgets.
        page          = gtk.VBox()
        paned         = gtk.VPaned()
        term          = vte.Terminal()
        wf_area       = gtk.EventBox()
        label, button = self._get_notebook_label()

        # Pack & Show.
        page.pack_start(paned, True)
        paned.pack1(term,    True,  False)
        paned.pack2(wf_area, False, True)
        self.notebook_terminal.append_page(page, label)
        page.show_all()

        # Configure widgets.
        paned.set_position(10000)
        wf_area.set_size_request(0, 0)
        self.terminals[page] = term
        self._open_connection(term)

        # Configure the notebook.
        self.notebook_terminal.set_current_page(-1)
        self.notebook_terminal.set_tab_reorderable(page, True)
        self.notebook_terminal.set_tab_label_packing(page, True, True, gtk.PACK_START)

        # Configure the terminal.
        # If we do not set the color twice (see again later) there 
        # is a black flicker when opening a new terminal.
        term.set_color_background(gtk.gdk.color_parse('white'))
        term.set_color_foreground(gtk.gdk.color_parse('black'))
        term.set_scrollback_lines(10000)
        term.set_size_request(100, 100)
        if self.font_size is None:
            self.font_size         = term.get_font().get_size()
            self.default_font_size = self.font_size
        else:
            self._update_terminal_font_size()
        # Again (see above).
        term.set_color_background(gtk.gdk.color_parse('white'))
        term.set_color_foreground(gtk.gdk.color_parse('black'))

        # Connect signals.
        button.connect('clicked',            self._tab_close_clicked_cb, page)
        term.connect('child-exited',         self._terminal_child_exited_cb)
        term.connect('window-title-changed', self._terminal_title_changed_cb)
        term.connect('key-press-event',      self._terminal_key_press_event_cb)
        return term


    def _remove_terminal(self, page = None):
        if len(self.terminals) == 0:
            return
        if page is None:
            selected = self.notebook_terminal.get_current_page()
            page     = self.notebook_terminal.get_nth_page(selected)
            term     = self.terminals[page]
        else:
            selected = self.notebook_terminal.page_num(page)
            term     = self.terminals[page]
        self._workflow_stop(term)
        del self.terminals[page]
        self.notebook_terminal.remove_page(selected)


    def _focus_terminal(self, widget, event):
        term = self._get_current_terminal()
        if term is None:
            return
        term.grab_focus()


    def _update_terminal_font_size(self):
        if len(self.terminals) == 0:
            return
        for terminal in self.terminals.itervalues():
            font = terminal.get_font()
            font.set_size(self.font_size)
            terminal.set_font(font)
            display = self._get_workflow_display(terminal)
            if display:
                self._update_workflow_display_font(display)


    def _get_current_terminal(self):
        selected = self.notebook_terminal.get_current_page()
        if selected < 0:
            return None
        page = self.notebook_terminal.get_nth_page(selected)
        return self.terminals[page]


    ################################################################
    # Terminal callbacks.
    ################################################################
    def _terminal_child_exited_cb(self, term):
        if self._workflow_is_running(term) and not self._confirm_close(term):
            self._open_connection(term)
            return
        self._remove_terminal()


    def _terminal_title_changed_cb(self, term):
        if term is None:
            self.window.set_title(cfg['APP_NAME'])
            return
        workflow   = self._get_workflow(term)
        term_title = title = term.get_window_title()
        if title is None:
            term_title = _('Terminal')
            title      = cfg['APP_NAME']
        if workflow is not None:
            title      += ' (%s)' % workflow.get_name()
            term_title += ' (%s)' % workflow.get_name()
        page  = term.get_parent().get_parent()
        label = self.notebook_terminal.get_tab_label(page)
        label.get_children()[0].set_text(term_title)
        self.notebook_terminal.set_tab_label(page, label)
        self.window.set_title(title)


    def _terminal_key_press_event_cb(self, widget, event):
        #print "Key pressed:", event.keyval
        if event.state == gtk.gdk.CONTROL_MASK:
            pass
        return False


    ################################################################
    # Notebook functions.
    ################################################################
    def _set_label_style(self, hbox, previous, button):
        w, h = gtk.icon_size_lookup_for_settings(button.get_settings(),
                                                 gtk.ICON_SIZE_MENU)
        button.set_size_request(w + 4, h + 4)


    def _get_notebook_label(self, text = ''):
        label = gtk.Label(text)
        label.set_alignment(.0, .5)
        label.set_padding(0, 0)
        label.set_ellipsize(pango.ELLIPSIZE_MIDDLE)
        label.set_single_line_mode(True)

        button = gtk.Button()
        button.set_property('can_focus', False)
        button.set_relief(gtk.RELIEF_NONE)
        button.set_focus_on_click(False)

        image = gtk.Image()
        image.set_from_stock('gtk-close', gtk.ICON_SIZE_MENU)
        button.add(image)

        hbox = gtk.HBox(spacing = 4)
        hbox.pack_start(label, True, True, 0)
        hbox.pack_end(button, False, False, 0)
        hbox.connect('style-set', self._set_label_style, button)
        hbox.show_all()
        return hbox, button


    ################################################################
    # Notebook callbacks.
    ################################################################
    def _tab_close_clicked_cb(self, button, page):
        term = self.terminals[page]
        if self._workflow_is_running(term) and not self._confirm_close(term):
            return
        self._remove_terminal(page)


    def _notebook_terminal_page_added_cb(self, widget, obj, page):
        self._notebook_terminal_switch_page_cb(widget, obj, 0)


    def _notebook_terminal_page_removed_cb(self, widget, obj, page):
        tab_open = widget.get_n_pages() > 0
        widget   = self.xml.get_widget('menu_tabs_close')
        widget.set_sensitive(tab_open)
        widget   = self.xml.get_widget('button_tab_close')
        widget.set_sensitive(tab_open)

        #widget   = self.xml.get_widget('menu_workflow_pause')
        #state    = widget.get_property('sensitive')
        #widget.set_sensitive(tab_open and state)
        #widget   = self.xml.get_widget('button_workflow_pause')
        #widget.set_sensitive(tab_open and state)

        widget   = self.xml.get_widget('menu_workflow_cancel')
        state    = widget.get_property('sensitive')
        widget.set_sensitive(tab_open and state)
        widget   = self.xml.get_widget('button_workflow_cancel')
        widget.set_sensitive(tab_open and state)

        #self.xml.get_widget('button_workflow_pause').set_active(False)

        term = self._get_current_terminal()
        self._terminal_title_changed_cb(term)


    def _notebook_terminal_switch_page_cb(self, widget, obj, pageno):
        page = self.notebook_terminal.get_nth_page(pageno)
        if not self.terminals.has_key(page):
            return
        term    = self.terminals[page]
        wf_open = self._workflow_is_open(term)

        # Set sensitivity.
        widget = self.xml.get_widget('menu_tabs_close')
        widget.set_sensitive(True)
        widget = self.xml.get_widget('button_tab_close')
        widget.set_sensitive(True)
        #widget = self.xml.get_widget('menu_workflow_pause')
        #widget.set_sensitive(wf_open)
        #widget = self.xml.get_widget('button_workflow_pause')
        #widget.set_sensitive(wf_open)
        widget = self.xml.get_widget('menu_workflow_cancel')
        widget.set_sensitive(wf_open)
        widget = self.xml.get_widget('button_workflow_cancel')
        widget.set_sensitive(wf_open)

        #self.xml.get_widget('button_workflow_pause').set_active(False)
        self._terminal_title_changed_cb(term)


    ################################################################
    # Menu callbacks.
    ################################################################
    def _recent_item_activated_cb(self, widget):
        from urlparse import urlparse
        uri      = widget.get_current_item().get_uri()
        name     = urlparse(uri).path
        term     = self._get_current_terminal()
        workflow = self.controller.workflow_db.get_workflow(name)
        if workflow is None:
            manager = gtk.recent_manager_get_default()
            manager.remove_item(uri)
            return
        self._workflow_stop(term)
        self._workflow_start(term, workflow)

    def _menu_file_quit_cb(self, widget):
        self._quit()


    def _menu_edit_copy_cb(self, widget):
        widget = self.window.get_focus()
        if not hasattr(widget, 'copy_clipboard'):
            return
        widget.copy_clipboard()


    def _menu_edit_paste_cb(self, widget):
        widget = self.window.get_focus()
        if not hasattr(widget, 'paste_clipboard'):
            return
        widget.paste_clipboard()


    def _menu_view_show_toolbar_cb(self, widget):
        if widget.get_active():
            self.toolbar.show()
        else:
            self.toolbar.hide()


    def _menu_view_zoom_in_cb(self, widget):
        self.font_size = int(self.font_size * 1.15)
        self._update_terminal_font_size()


    def _menu_view_zoom_out_cb(self, widget):
        self.font_size = int(self.font_size / 1.15)
        self._update_terminal_font_size()


    def _menu_view_normal_size_cb(self, widget):
        self.font_size = self.default_font_size
        self._update_terminal_font_size()


    def _menu_tabs_new_cb(self, widget):
        self._add_terminal()


    def _menu_tabs_close_cb(self, widget):
        term = self._get_current_terminal()
        if self._workflow_is_running(term) and not self._confirm_close(term):
            return
        self._remove_terminal()


    def _menu_tabs_previous_tab_cb(self, widget):
        self.notebook_terminal.prev_page()


    def _menu_tabs_next_tab_cb(self, widget):
        self.notebook_terminal.next_page()


    def _menu_tabs_move_left_cb(self, widget):
        selected = self.notebook_terminal.get_current_page()
        n_pages  = self.notebook_terminal.get_n_pages()
        page     = self.notebook_terminal.get_children()[selected]
        pos      = (selected - 1) % n_pages
        self.notebook_terminal.reorder_child(page, pos)


    def _menu_tabs_move_right_cb(self, widget):
        selected = self.notebook_terminal.get_current_page()
        n_pages  = self.notebook_terminal.get_n_pages()
        page     = self.notebook_terminal.get_children()[selected]
        pos      = (selected + 1) % n_pages
        self.notebook_terminal.reorder_child(page, pos)


    def _menu_workflow_select_cb(self, widget):
        self.emit('select-workflow')


    def _menu_workflow_pause_cb(self, widget):
        term = self._get_current_terminal()
        self._workflow_pause(term)
        active = widget.get_active()
        self.ignore_signal = True
        self.xml.get_widget('button_workflow_pause').set_active(active)
        self.ignore_signal = False


    def _menu_workflow_cancel_cb(self, widget):
        term = self._get_current_terminal()
        self._workflow_stop(term)


    def _menu_workflow_create_cb(self, widget):
        self.emit('edit-workflow', Workflow())


    def _menu_help_about_cb(self, widget):
        dialog_about = AboutDialog()
        dialog_about.run()


    ################################################################
    # Toolbutton callbacks.
    ################################################################
    def _button_tab_new_clicked_cb(self, button):
        self._menu_tabs_new_cb(button)


    def _button_tab_close_clicked_cb(self, button):
        self._menu_tabs_close_cb(button)


    def _button_workflow_select_clicked_cb(self, button):
        self._menu_workflow_select_cb(button)


    def _button_workflow_pause_toggled_cb(self, button):
        if self.ignore_signal:
            return
        self.xml.get_widget('menu_workflow_pause').activate()


    def _button_workflow_cancel_clicked_cb(self, button):
        self._menu_workflow_cancel_cb(button)


    def _button_zoom_in_clicked_cb(self, button):
        self._menu_view_zoom_in_cb(button)


    def _button_zoom_out_clicked_cb(self, button):
        self._menu_view_zoom_out_cb(button)


    ################################################################
    # Other callbacks.
    ################################################################
    def _quit(self, *args):
        while self.notebook_terminal.get_n_pages() > 0:
            term = self._get_current_terminal()
            if self._workflow_is_running(term) and \
              not self._confirm_close(term):
                return True
            self._remove_terminal()
        gtk.main_quit()

gobject.type_register(MainWindow)

gobject.signal_new('select-workflow',
                   MainWindow,
                   gobject.SIGNAL_RUN_FIRST,
                   gobject.TYPE_NONE,
                   ())

gobject.signal_new('edit-workflow',
                   MainWindow,
                   gobject.SIGNAL_RUN_FIRST,
                   gobject.TYPE_NONE,
                   (gobject.TYPE_PYOBJECT,))
